/**
 * Moondance UML modeling tool
 * 
 * @author Dascalu Laurentiu (dascalu.laurentziu@gmail.com).
 * 
 * This program is free software; you can redistribute it and
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package ro.pub.cs.se.moondance.code;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.ui.wizards.JavaCapabilityConfigurationPage;

import ro.pub.cs.se.moondance.MoondanceData;
import ro.pub.cs.se.moondance.UMLPageSerialized;
import ro.pub.cs.se.moondance.model.AggregationConnection;
import ro.pub.cs.se.moondance.model.ClassFunction;
import ro.pub.cs.se.moondance.model.ClassVariable;
import ro.pub.cs.se.moondance.model.CompositionConnection;
import ro.pub.cs.se.moondance.model.Connection;
import ro.pub.cs.se.moondance.model.Parameter;
import ro.pub.cs.se.moondance.model.UMLClass;
import ro.pub.cs.se.moondance.model.UMLDiagram;
import ro.pub.cs.se.moondance.model.UMLEntity;
import ro.pub.cs.se.moondance.model.UMLInterface;
import ro.pub.cs.se.moondance.model.UMLPackage;

public class JavaCodeGenerator extends ICodeGenerator {

	// TODO: JAVA PACKET GENERATION !!!!!
	private final static String JAVA_SRC = "src/";
	private final static String JAVA_SOURCE = ".java";

	private IProject iProj;

	private String variableToString(ClassVariable cVar) {
		String data = cVar.getVisibility() + " ";

		if (cVar.isStatic())
			data += "static ";
		data += cVar.getType();

		if (cVar.getMultiplicity() >= 0)
			data += "[" + cVar.getMultiplicity() + "]";
		data += " ";

		data += cVar.getName();

		if (cVar.getInitialValue() != null
				&& !cVar.getInitialValue().equals(""))
			data += " = " + cVar.getInitialValue() + ";\n";
		else
			data += ";\n";

		return data;
	}

	private String functionHeaderToString(ClassFunction cFun,
			boolean isClassAbstract, boolean isInterface) {

		// Generate nothing for destructors; let the GB do this job
		if (cFun.isDestructor())
			return "";

		String data = cFun.getVisibility() + " ";

		if (cFun.isConstructor()) {
			// Constructor
			System.out.println(cFun.getName());
			data += cFun.getName() + "(";

			ArrayList<Parameter> params = cFun.getParameters();

			for (int i = 0; i < params.size(); i++) {
				Parameter param = params.get(i);
				data += param.getType();

				if (param.getMultiplicity())
					data += "[]";
				data += " " + param.getName();
				if (i != params.size() - 1)
					data += ", ";
			}

			if (isInterface)
				data += ");\n\n";
			else
				data += ")\n{\n}\n\n";
		} else {
			// Ordinary function
			if (cFun.isStatic())
				data += "static ";
			data += cFun.getReturnType() + " " + cFun.getName() + "(";

			ArrayList<Parameter> params = cFun.getParameters();

			for (int i = 0; i < params.size(); i++) {
				Parameter param = params.get(i);
				data += param.getType();

				if (param.getMultiplicity())
					data += "[]";
				data += " " + param.getName();
				if (i != params.size() - 1)
					data += ", ";
			}

			if (isInterface)
				data += ");\n\n";
			else
				data += ")\n{\n}\n\n";
		}
		return data;
	}

	private String generateClassCode(UMLClass myClass) {
		String dataHeader = myClass.getVisibility() + " ";
		String additionalInclude = "";

		if (myClass.isAbstract())
			dataHeader += "abstract ";
		dataHeader += "class " + myClass.getName() + " ";

		ArrayList<UMLInterface> interfaces = myClass.getImplementedInterfaces();
		ArrayList<UMLClass> classes = myClass.getExtendedClasses();

		for (int i = 0; i < interfaces.size() + classes.size(); i++) {
			if (i < classes.size())
				dataHeader += "extends " + classes.get(i).getName();
			else
				dataHeader += "implements "
						+ interfaces.get(i - classes.size()).getName();
			if (i < interfaces.size() + classes.size() - 1)
				dataHeader += ", ";
		}

		dataHeader += "\n{\n";

		ArrayList<String> references = new ArrayList<String>();

		// Add aggregation/composition members, if any
		for (int i = 0; i < myClass.getTargetConnections().size(); i++) {
			Connection conn = myClass.getTargetConnections().get(i);
			if (conn instanceof CompositionConnection
					|| conn instanceof AggregationConnection) {
				references.add(conn.getSource().toString());
			}
		}

		if (references.size() > 0) {
			dataHeader += "// Autogenerated class references to this aggregate/composite class\n";

			for (int i = 0; i < references.size(); i++)
				dataHeader += "private " + references.get(i) + "[] "
						+ "refence" + references.get(i) + ";\n";
			dataHeader += "\n\n";
		}

		// Add the members grouped by visibility
		ArrayList<ClassVariable> cVars = myClass.getAttributes();

		for (int i = 0; i < cVars.size(); i++)
			dataHeader += variableToString(cVars.get(i));
		dataHeader += "\n";

		// Add the methods
		ArrayList<ClassFunction> cFuns = myClass.getOperations();

		for (int i = 0; i < cFuns.size(); i++)
			dataHeader += functionHeaderToString(cFuns.get(i), cFuns.get(i)
					.isAbstract(), false);

		dataHeader += "};\n";

		return additionalInclude + dataHeader;
	}

	private String generateInterfaceCode(UMLInterface myInterface) {
		String dataHeader = myInterface.getVisibility() + " interface "
				+ myInterface.getName() + "\n{\n";

		// Add the members
		ArrayList<ClassVariable> cVars = myInterface.getAttributes();

		for (int i = 0; i < cVars.size(); i++)
			dataHeader += variableToString(cVars.get(i));

		// Add the methods
		ArrayList<ClassFunction> cFuns = myInterface.getOperations();

		for (int i = 0; i < cFuns.size(); i++)
			dataHeader += functionHeaderToString(cFuns.get(i), true, true);

		dataHeader += "};\n";

		return dataHeader;
	}

	private void createPacketFolders(String packetName) throws Exception {
		if (iProj instanceof IContainer) {
			StringTokenizer strtok = new StringTokenizer(packetName + ".", ".");
			String currentdir = JAVA_SRC;

			IContainer container = (IContainer) iProj;

			while (strtok.hasMoreTokens()) {
				String packet = strtok.nextToken();
				currentdir += packet + "/";
				IFolder folder = container.getFolder(new Path(currentdir));
				if (!folder.exists())
					folder.create(true, true, MoondanceData.DEFAULT_MONITOR);
			}

		} else
			throw new Exception();
	}

	private void generatePacket(UMLPackage myPacket, String prefix)
			throws Exception {
		String packetName = prefix + myPacket.getName();

		createPacketFolders(packetName);

		for (int i = 0; i < myPacket.getContainedClasses().size(); i++) {
			String data = "package " + packetName + ";\n\n";

			if (myPacket.getContainedClasses().get(i) instanceof UMLInterface)
				data += generateInterfaceCode((UMLInterface) myPacket
						.getContainedClasses().get(i));
			else if (myPacket.getContainedClasses().get(i) instanceof UMLClass)
				data += generateClassCode((UMLClass) myPacket
						.getContainedClasses().get(i));

			createNewFile(iProj, JAVA_SRC + packetName.replace('.', '/') + "/"
					+ myPacket.getContainedClasses().get(i).getName()
					+ JAVA_SOURCE, data.getBytes());
		}

		for (int i = 0; i < myPacket.getContainedPackages().size(); i++)
			generatePacket(myPacket.getContainedPackages().get(i), packetName
					+ ".");
	}

	private void generateCode(UMLEntity block) throws Exception {
		String name = block.toString();
		String sourceFile = JAVA_SRC + name + JAVA_SOURCE;

		String data = null;

		if (block instanceof UMLClass) {
			data = generateClassCode((UMLClass) block);
		} else if (block instanceof UMLInterface) {
			data = generateInterfaceCode((UMLInterface) block);
		} else if (block instanceof UMLPackage) {
			generatePacket((UMLPackage) block, "");
		}

		if (data != null)
			createNewFile(iProj, sourceFile, data.getBytes());
	}

	@Override
	public void generateCode() {
		/**
		 * From the project name I eliminate the extension and given that name
		 * to the new created project.
		 * 
		 * Example: Example1.uml.moondance => Example1
		 */
		String projectName = MoondanceData.getCurrentDiagram().getDiagramName();
		projectName = projectName.substring(0, projectName.length()
				- UMLPageSerialized.DEFAULT_EXTENSION.length());

		iProj = ResourcesPlugin.getWorkspace().getRoot()
				.getProject(projectName);

		try {
			if (iProj.exists())
				iProj.delete(true, MoondanceData.DEFAULT_MONITOR);

			JavaCapabilityConfigurationPage jcpage = new JavaCapabilityConfigurationPage();
			JavaCapabilityConfigurationPage.createProject(iProj, (URI) null,
					MoondanceData.DEFAULT_MONITOR);

			IJavaProject ijava = JavaCore.create(iProj);
			jcpage.init(ijava, null, null, false);
			jcpage.configureJavaProject(null);

			// For-each block (class, abstract class and interface)
			// generate a pair (source file, header file)
			// Generation involves: file creation, block code generation
			// and linking between blocks (inheritance etc)
			UMLDiagram diagram = MoondanceData.getCurrentDiagram();

			List<UMLEntity> blocksList = diagram.getChildren();

			for (int i = 0; i < blocksList.size(); i++)
				generateCode(blocksList.get(i));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void run() {
		generateCode();
	}
}
